/*
 * Copyright (c) 2014-2015 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */

#include <Swift/QtUI/Trellis/QtDNDTabBar.h>

#include <cassert>

#include <QDrag>
#include <QDropEvent>
#include <QMimeData>
#include <QMouseEvent>
#include <QPainter>
#include <QTabWidget>

namespace Swift {

QtDNDTabBar::QtDNDTabBar(QWidget* parent) : QTabBar(parent) {
	setAcceptDrops(true);

	// detect the default tab bar height;
	insertTab(0, "");
	defaultTabHeight = QTabBar::sizeHint().height();
	removeTab(0);
}

QtDNDTabBar::~QtDNDTabBar() {

}

int QtDNDTabBar::getDragIndex() const {
	return dragIndex;
}

QString QtDNDTabBar::getDragText() const {
	return dragText;
}

QWidget* QtDNDTabBar::getDragWidget() const {
	return dragWidget;
}

QSize QtDNDTabBar::sizeHint() const {
	QSize hint = QTabBar::sizeHint();
	if (hint.isEmpty()) {
		hint = QSize(parentWidget()->width(), defaultTabHeight);
	}
	return hint;
}

QSize QtDNDTabBar::tabSizeHint(int index) const {
	QSize tabSize = QTabBar::tabSizeHint(index);
#if defined(Q_OS_MAC)
	// With multiple tabs having the same label in a QTabBar, the size hint computed by
	// Qt on OS X is too small and it is elided even though there is enough horizontal
	// space available. We work around this issue by adding the width of a letter to the
	// size hint.
	tabSize += QSize(QFontMetrics(font()).width("I"), 0);
#endif
	return tabSize;
}

void QtDNDTabBar::dragEnterEvent(QDragEnterEvent* dragEnterEvent) {
	QtDNDTabBar* sourceTabBar = dynamic_cast<QtDNDTabBar*>(dragEnterEvent->source());
	if (sourceTabBar) {
		dragEnterEvent->acceptProposedAction();
	}
}

void QtDNDTabBar::dropEvent(QDropEvent* dropEvent) {
	QtDNDTabBar* sourceTabBar = dynamic_cast<QtDNDTabBar*>(dropEvent->source());
	if (sourceTabBar && dropEvent->mimeData() && dropEvent->mimeData()->data("action") == QByteArray("application/tab-detach")) {
		QtDNDTabBar* source = dynamic_cast<QtDNDTabBar*>(dropEvent->source());

		int targetTabIndex = tabAt(dropEvent->pos());
		QRect rect = tabRect(targetTabIndex);
		if (targetTabIndex >= 0 && (dropEvent->pos().x() - rect.left() - rect.width()/2 > 0)) {
			targetTabIndex++;
		}

		QWidget* tab = source->getDragWidget();
		assert(tab);
		QTabWidget* targetTabWidget = dynamic_cast<QTabWidget*>(parentWidget());

		QString tabText = source->getDragText();

		/*
		 * When you add a widget to an empty QTabWidget, it's automatically made the current widget.
		 * Making the widget the current widget, widget->show() is called for the widget. Directly reacting
		 * to that event, and adding the widget again to the QTabWidget results in undefined behavior. For
		 * example the tab label is shown but the widget is neither has the old nor in the new QTabWidget as
		 * parent. Blocking signals on the QWidget to be added to a QTabWidget prevents this behavior.
		 */
		targetTabWidget->setUpdatesEnabled(false);
		tab->blockSignals(true);
		targetTabWidget->insertTab(targetTabIndex, tab, tabText);
		dropEvent->acceptProposedAction();
		tab->blockSignals(false);
		targetTabWidget->setUpdatesEnabled(true);
		onDropSucceeded();
	}
}

bool QtDNDTabBar::event(QEvent* event) {
	QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event);
	if (mouseEvent) {
		QWidget* childAtPoint = window()->childAt(mapTo(window(), mouseEvent->pos()));
		QtDNDTabBar* underMouse = dynamic_cast<QtDNDTabBar*>(childAtPoint);
		if (!underMouse && childAtPoint) {
			underMouse = dynamic_cast<QtDNDTabBar*>(childAtPoint->parent());
		}
		if (!underMouse && currentIndex() >= 0) {
			// detach and drag

			// stop move event
			QMouseEvent* finishMoveEvent = new QMouseEvent (QEvent::MouseMove, mouseEvent->pos (), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
			QTabBar::event(finishMoveEvent);
			delete finishMoveEvent;
			finishMoveEvent = NULL;

			// start drag
			QDrag* drag = new QDrag(this);
			QMimeData* mimeData = new QMimeData;

			// distinguish tab-reordering drops from other ones
			mimeData->setData("action", "application/tab-detach") ;
			drag->setMimeData(mimeData);

			// set drag image
			QRect rect = tabRect( currentIndex() );
#if QT_VERSION >= 0x050000
			QPixmap pixmap = grab(rect);
#else
			QPixmap pixmap = QPixmap::grabWidget(this, rect);
#endif
			QPixmap targetPixmap (pixmap.size ());
			QPainter painter (&targetPixmap);
			painter.setOpacity(0.9);
			painter.drawPixmap(0,0, pixmap);
			painter.end ();
			drag->setPixmap (targetPixmap);

			drag->setHotSpot(QPoint(drag->pixmap().width()/2, drag->pixmap().height()));

			dragIndex = currentIndex();
			dragText = tabText(dragIndex);
			dragWidget = dynamic_cast<QTabWidget*>(parent())->widget(dragIndex);
			assert(dragWidget);
			dynamic_cast<QTabWidget*>(parent())->removeTab(currentIndex());
			Qt::DropAction dropAction = drag->exec();
			if (dropAction == Qt::IgnoreAction) {
				// aborted drag, put tab back in place
				// disable event handling during the insert for the tab to prevent infinite recursion (stack overflow)
				dragWidget->blockSignals(true);
				dynamic_cast<QTabWidget*>(parent())->insertTab(dragIndex, dragWidget, dragText);
				dragWidget->blockSignals(false);
			}
			return true;
		}
	}
	return QTabBar::event(event);
}

}
